{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8cc39cad",
   "metadata": {},
   "source": [
    "# Groundedness Evaluator\n",
    "\n",
    "답변이 주어진 문맥(context)에 기반하여 정확한지 평가하는 Evaluator 입니다.\n",
    "\n",
    "이 Evaluator 는 RAG 의 답변에 대한 할루시네이션(Hallucination)을 평가하는데 활용할 수 있습니다.\n",
    "\n",
    "이번 튜토리얼에서는 Upstage Groundness Checker와 직업 커스텀하여 만든 Groundness Checker를 활용하여 Groundedness를 평가하는 방법을 살펴보겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34a30a84",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 설치\n",
    "# !pip install -qU langsmith langchain-teddynote"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d75d1492",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API KEY를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API KEY 정보로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "633d9db2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH16-Evaluations\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a09c8411",
   "metadata": {},
   "source": [
    "## RAG 성능 테스트를 위한 함수 정의"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "011f17eb",
   "metadata": {},
   "source": [
    "테스트에 활용할 RAG 시스템을 생성하겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bb9be51",
   "metadata": {},
   "outputs": [],
   "source": [
    "from myrag import PDFRAG\n",
    "\n",
    "\n",
    "# 질문에 대한 답변하는 함수를 생성\n",
    "def ask_question_with_llm(llm):\n",
    "    # PDFRAG 객체 생성\n",
    "    rag = PDFRAG(\n",
    "        \"data/SPRI_AI_Brief_2023년12월호_F.pdf\",\n",
    "        llm,\n",
    "    )\n",
    "\n",
    "    # 검색기(retriever) 생성\n",
    "    retriever = rag.create_retriever()\n",
    "\n",
    "    # 체인(chain) 생성\n",
    "    rag_chain = rag.create_chain(retriever)\n",
    "\n",
    "    def _ask_question(inputs: dict):\n",
    "        # 질문에 대한 컨텍스트 검색\n",
    "        context = retriever.invoke(inputs[\"question\"])\n",
    "        # 검색된 문서들을 하나의 문자열로 결합\n",
    "        context = \"\\n\".join([doc.page_content for doc in context])\n",
    "        # 질문, 컨텍스트, 답변을 포함한 딕셔너리 반환\n",
    "        return {\n",
    "            \"question\": inputs[\"question\"],\n",
    "            \"context\": context,\n",
    "            \"answer\": rag_chain.invoke(inputs[\"question\"]),\n",
    "        }\n",
    "\n",
    "    return _ask_question"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af0908d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "gpt_chain = ask_question_with_llm(ChatOpenAI(model=\"gpt-4o-mini\", temperature=0))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23a00743",
   "metadata": {},
   "source": [
    "## UpstageGroundednessCheck\n",
    "\n",
    "업스테이지(Upstage) 의 Groundedness Check 기능을 활용하기 위해서는 아래 링크에서 API 키를 발급받아야 합니다.\n",
    "\n",
    "- [API 키 발급](https://console.upstage.ai/api-keys)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1a2be64e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_upstage import UpstageGroundednessCheck\n",
    "\n",
    "# 업스테이지 Groundness Checker 생성\n",
    "upstage_groundedness_check = UpstageGroundednessCheck()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8311d89f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Groundness Checker 를 실행하여 평가\n",
    "request_input = {\n",
    "    \"context\": \"테디의 성별은 남자이며, 테디노트 유튜브 채널을 운영하고 있습니다.\",\n",
    "    \"answer\": \"테디는 남자다.\",\n",
    "}\n",
    "\n",
    "response = upstage_groundedness_check.invoke(request_input)\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f6a528b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Groundness Checker 를 실행하여 평가\n",
    "request_input = {\n",
    "    \"context\": \"테디의 성별은 남자이며, 테디노트 유튜브 채널을 운영하고 있습니다.\",\n",
    "    \"answer\": \"테디는 여자다.\",\n",
    "}\n",
    "\n",
    "response = upstage_groundedness_check.invoke(request_input)\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb85e87a",
   "metadata": {},
   "source": [
    "UpstageGroundednessCheck Evaluator 를 정의합니다. 추후 Evaluate 함수에서 활용합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6422a0be",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.schemas import Run, Example\n",
    "from langsmith.evaluation import evaluate\n",
    "\n",
    "\n",
    "def upstage_groundness_check_evaluator(run: Run, example: Example) -> dict:\n",
    "    # LLM 생성 답변, 정답 답변 가져오기\n",
    "    answer = run.outputs.get(\"answer\", \"\")\n",
    "    context = run.outputs.get(\"context\", \"\")\n",
    "\n",
    "    # Groundness 체크\n",
    "    groundedness_score = upstage_groundedness_check.invoke(\n",
    "        {\"answer\": answer, \"context\": context}\n",
    "    )\n",
    "    groundedness_score = groundedness_score == \"grounded\"\n",
    "\n",
    "    return {\"key\": \"groundness_score\", \"score\": int(groundedness_score)}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e12e6c1e",
   "metadata": {},
   "source": [
    "## langchain_teddynote Groundness Checker\n",
    "\n",
    "OpenAI 의 모델을 활용하여 커스텀한 Groundness Checker 를 생성합니다.\n",
    "\n",
    "OpenAI 모델을 활용하여 Groundedness 를 체크합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ec51d0b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.schemas import Run, Example\n",
    "from langchain_teddynote.evaluator import GroundnessChecker\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# teddynote Groundness Checker 생성\n",
    "groundedness_check = GroundnessChecker(\n",
    "    ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n",
    ").create()\n",
    "\n",
    "\n",
    "def teddynote_groundness_check_evaluator(run: Run, example: Example) -> dict:\n",
    "    # LLM 생성 답변, 정답 답변 가져오기\n",
    "    answer = run.outputs.get(\"answer\", \"\")\n",
    "    context = run.outputs.get(\"context\", \"\")\n",
    "\n",
    "    # Groundness 체크\n",
    "    groundedness_score = groundedness_check.invoke(\n",
    "        {\"answer\": answer, \"context\": context}\n",
    "    )\n",
    "    groundedness_score = groundedness_score.score == \"yes\"\n",
    "\n",
    "    return {\"key\": \"groundness_score\", \"score\": int(groundedness_score)}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2244d30",
   "metadata": {},
   "source": [
    "Groundedness 평가를 실행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d856d3a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.evaluation import evaluate\n",
    "\n",
    "# 데이터셋 이름 설정\n",
    "dataset_name = \"RAG_EVAL_DATASET\"\n",
    "\n",
    "# 실행\n",
    "experiment_results = evaluate(\n",
    "    gpt_chain,\n",
    "    data=dataset_name,\n",
    "    evaluators=[\n",
    "        upstage_groundness_check_evaluator,\n",
    "        teddynote_groundness_check_evaluator,\n",
    "    ],\n",
    "    experiment_prefix=\"GROUNDEDNESS-EVAL\",\n",
    "    # 실험 메타데이터 지정\n",
    "    metadata={\n",
    "        \"variant\": \"Upstage & teddynote Groundness Checker 를 활용한 Hallucination 평가\",\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a080eec0",
   "metadata": {},
   "source": [
    "![](./assets/output-09.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b5b3fd9",
   "metadata": {},
   "source": [
    "## Summary Evaluators 를 활용한 데이터셋에 대한 종합 평가\n",
    "\n",
    "데이터셋 전체에 대한 Groundedness 평가를 실행할 때 유용합니다. (이전 단계는 개별 데이터에 대한 평가를 수행하였습니다.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c1a554d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "from langsmith.schemas import Example, Run\n",
    "\n",
    "\n",
    "def upstage_groundness_check_summary_evaluator(\n",
    "    runs: List[Run], examples: List[Example]\n",
    ") -> dict:\n",
    "    def is_grounded(run: Run) -> bool:\n",
    "        context = run.outputs[\"context\"]\n",
    "        answer = run.outputs[\"answer\"]\n",
    "        return (\n",
    "            upstage_groundedness_check.invoke({\"context\": context, \"answer\": answer})\n",
    "            == \"grounded\"\n",
    "        )\n",
    "\n",
    "    groundedness_scores = sum(1 for run in runs if is_grounded(run))\n",
    "    return {\"key\": \"groundness_score\", \"score\": groundedness_scores / len(runs)}\n",
    "\n",
    "\n",
    "def teddynote_groundness_check_summary_evaluator(\n",
    "    runs: List[Run], examples: List[Example]\n",
    ") -> dict:\n",
    "    def is_grounded(run: Run) -> bool:\n",
    "        context = run.outputs[\"context\"]\n",
    "        answer = run.outputs[\"answer\"]\n",
    "        return (\n",
    "            groundedness_check.invoke({\"context\": context, \"answer\": answer}).score\n",
    "            == \"yes\"\n",
    "        )\n",
    "\n",
    "    groundedness_scores = sum(1 for run in runs if is_grounded(run))\n",
    "    return {\"key\": \"groundness_score\", \"score\": groundedness_scores / len(runs)}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4db88640",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.evaluation import evaluate\n",
    "\n",
    "# 평가 실행\n",
    "experiment_result1 = evaluate(\n",
    "    gpt_chain,\n",
    "    data=dataset_name,\n",
    "    summary_evaluators=[\n",
    "        upstage_groundness_check_summary_evaluator,\n",
    "    ],\n",
    "    experiment_prefix=\"GROUNDNESS_UPSTAGE_SUMMARY_EVAL\",\n",
    "    # 실험 메타데이터 지정\n",
    "    metadata={\n",
    "        \"variant\": \"Upstage Groundness Checker 를 활용한 Hallucination 평가\",\n",
    "    },\n",
    ")\n",
    "\n",
    "# 평가 실행\n",
    "experiment_result2 = evaluate(\n",
    "    gpt_chain,\n",
    "    data=dataset_name,\n",
    "    summary_evaluators=[\n",
    "        teddynote_groundness_check_summary_evaluator,\n",
    "    ],\n",
    "    experiment_prefix=\"GROUNDNESS_TEDDYNOTE_SUMMARY_EVAL\",\n",
    "    # 실험 메타데이터 지정\n",
    "    metadata={\n",
    "        \"variant\": \"Teddynote Groundness Checker 를 활용한 Hallucination 평가\",\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e4943d9",
   "metadata": {},
   "source": [
    "![](./assets/output-10.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
